<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>ConcurrentHashMap v7</title>
    <style>
        div {
            margin-top: 10px;
        }

        html {
            font-size: 12px;
        }

        input[type=text] {
            width: 50px;
        }

        input.long {
            width: 160px;
        }

        ::-webkit-input-placeholder {
            color: white;
        }

        table {
            border-collapse: collapse;
            border-spacing: 0;
        }

        th {
            border-top: 2px solid black;
        }

        td {
            border-bottom: 1px solid black;
        }
    </style>
</head>

<body>
    <div>
        <label for="capacity">capacity </label><input type="text" id="capacity" value="16" placeholder="请输入容量">
        <label for="factor">factor </label><input type="text" id="factor" value="0.75" placeholder="负载因子">
        <label for="clevel">clevel </label><input type="text" id="clevel" value="16" placeholder="并发度">
        <input type="button" value="init" id="initBtn">
        <input type="button" value="clear" id="clearBtn">
    </div>
    <div>
        <table id="tbl">
            <tr>
                <th>线程</th>
                <th>keys 以,分隔</th>
                <th colspan="6">计算哈希码</th>
                <th>暂停时间</th>
                <th>操作</th>
                <th>执行时间</th>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td>原始hash</td>
                <td>二次hash</td>
                <td>Segment下标</td>
                <td>桶下标</td>
                <td>固定hash</td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr id="template" style="display:none">
                <td>线程 - <span class="num"></span></td>
                <td><input type="text" class="key long"></td>
                <td><input type="button" value="index" class="index"></td>
                <td><input type="text" readonly class="codes long"></td>
                <td><input type="text" readonly class="hashes long"></td>
                <td><input type="text" readonly class="segmentIndexes long"></td>
                <td><input type="text" readonly class="indexes long"></td>
                <td><input type="text" value="-1" class="fix"></td>
                <td><input type="text" value="0" class="seconds"></td>
                <td>
                    <input type="button" value="put" class="put">
                    <input type="button" value="remove" class="remove">
                </td>
                <td><span class="timeout"></span></td>
            </tr>
        </table>
    </div>
    <div id="canvasDiv">
        <canvas id="canvas" width="1024" height="400"></canvas>
    </div>
    <script>
        const b_colors = ["#D24D57", "#EB7347", "#FC9D99", "#26A65B", "#AEDD81", "#84AF9B", "#00CCFF", "#D0D0D0", "#2C3E50"]
        const f_colors = ["white", "white", "black", "white", "black", "white", "black", "black", "white"]
        let idx = 1
        function addRow() {
            let row = document.getElementById("template").cloneNode(true)
            let parent = document.getElementById("template").parentNode
            row.removeAttribute("id")
            row.querySelector("td:nth-child(1) span").innerText = idx++
            let random = Math.floor(Math.random() * 9)
            row.style.display = "table-row"
            row.style.backgroundColor = b_colors[random]
            row.style.color = f_colors[random]
            parent.appendChild(row)
        }
        addRow()
        addRow()
        document.getElementById("initBtn").onclick = function () {
            let capacity = document.getElementById("capacity").value;
            let factor = document.getElementById("factor").value;
            let clevel = document.getElementById("clevel").value;
            let url = `http://localhost:8080/cv7/map/init?capacity=${capacity}&factor=${factor}&clevel=${clevel}`;
            fetch(url).then(r => r.json()).then(json => {
                loadData();
            });
        }
        document.getElementById("clearBtn").onclick = function () {
            let url = `http://localhost:8080/cv7/map/clear`;
            fetch(url).then(r => r.json()).then(json => {
                loadData();
            });
        }
        document.getElementById("tbl").addEventListener("click", function (e) {
            e.stopPropagation();
            let type = e.target.className;
            let tr = e.target.parentNode.parentNode;
            let span = tr.querySelector(".timeout");
            if (type === 'remove') {
                let key = tr.querySelector('.key').value;
                let fix = tr.querySelector('.fix').value;
                let seconds = tr.querySelector('.seconds').value;
                let url = `http://localhost:8080/cv7/map/remove?key=${key}&fix=${fix}&seconds=${seconds}`;
                let time = seconds;
                span.innerText = time--;
                let t = setInterval(function () {
                    span.innerText = time--;
                }, 1000);
                fetch(url).then(r => r.json()).then(json => {
                    clearInterval(t);
                    span.innerText = "";
                    loadData();
                });
            } else if (type === 'put') {
                let key = tr.querySelector('.key').value;
                let fix = tr.querySelector('.fix').value;
                let seconds = tr.querySelector('.seconds').value;
                let url = `http://localhost:8080/cv7/map/put?key=${key}&fix=${fix}&seconds=${seconds}`;
                let time = seconds;
                span.innerText = time--;
                let t = setInterval(function () {
                    span.innerText = time--;
                }, 1000);
                fetch(url).then(r => r.json()).then(json => {
                    clearInterval(t);
                    span.innerText = "";
                    loadData();
                });
            } else if (type === 'index') {
                let key = tr.querySelector(".key").value;
                let fix = tr.querySelector(".fix").value;
                let capacity = document.getElementById("capacity").value;
                let url = `http://localhost:8080/cv7/map/index?key=${key}&fix=${fix}&capacity=${capacity}`;
                fetch(url).then(r => r.json()).then(json => {
                    tr.querySelector(".codes").value = json.codes.join(",");
                    tr.querySelector(".hashes").value = json.hashes.join(",");
                    tr.querySelector(".indexes").value = json.indexes.join(",");
                    tr.querySelector(".segmentIndexes").value = json.segmentIndexes.join(",");
                });
            }
        })


        const RECT_WIDTH = 20;
        const RECT_HEIGHT = 16;
        const SPACEING = 3;
        const CANVAS_WIDTH = 1024;
        const CANVAS_HEIGHT = 300;
        const ARROW_LENGTH = 10;
        class Rect {
            constructor(x, y, w, h, c, v, vx, vy) {
                this.x = x;     // 大矩形 x
                this.y = y;     // 大矩形 y
                this.w = w;     // 大矩形 w
                this.h = h;     // 大矩形 h
                this.c = c;     // 大矩形 c
                this.v = v;     // 大矩形 v 即值
                this.vx = vx;   // 值 x
                this.vy = vy;   // 值 y
            }
        }
        class Circle {
            constructor(x, y, r, c, v, vx, vy) {
                this.x = x;     // 圆 x
                this.y = y;     // 圆 y
                this.r = r;     // 直径
                this.c = c;     // 圆 c
                this.v = v;     // 圆 v 即值
                this.vx = vx;   // 值 x
                this.vy = vy;   // 值 y
            }
        }
        class Arrow {
            constructor(fx, fy, tx, ty, c) {
                this.fx = fx;
                this.fy = fy;
                this.tx = tx;
                this.ty = ty;
                this.c = c;
            }
        }
        function addToSelect(idx, round) {
            let option = document.createElement("option");
            option.value = idx;
            option.innerHTML = (round ? "[*]" : "") + "frame -> " + idx;
            document.getElementById("fs").appendChild(option);
        }
        function clearSelect() {
            document.getElementById("fs").innerHTML = "";
        }
        /* document.getElementById("fs").onchange = function () {
            dd.drawFrame(this.value - 0)
        } */
        class Draw {
            constructor() {
                this.canvas = document.querySelector('canvas');
                this.ctx = document.querySelector('canvas').getContext('2d');
                this.frames = [];
                this.current = 0;
            }
            reset() {
                this.canvas.width = CANVAS_WIDTH;
                this.canvas.height = CANVAS_HEIGHT;
            }
            drawArrow(a) {
                let { fx, fy, tx, ty, c } = { ...a };
                let theta = 30;
                let headlen = 10;
                let angle = Math.atan2(fy - ty, fx - tx) * 180 / Math.PI;
                let a1 = (angle + theta) * Math.PI / 180;
                let a2 = (angle - theta) * Math.PI / 180;
                let topX = headlen * Math.cos(a1);
                let topY = headlen * Math.sin(a1);
                let bottomX = headlen * Math.cos(a2);
                let bottomY = headlen * Math.sin(a2);

                this.ctx.save();
                // this.ctx.translate(0, CANVAS_HEIGHT - RECT_WIDTH - ARROW_TOP);
                this.ctx.lineWidth = .5;
                this.ctx.beginPath();
                let ax = fx - topX;
                let ay = fy - topY;

                this.ctx.moveTo(ax, ay);
                this.ctx.moveTo(fx, fy);
                this.ctx.lineTo(tx, ty);

                ax = tx + topX;
                ay = ty + topY;
                this.ctx.moveTo(ax, ay);
                this.ctx.lineTo(tx, ty);

                ax = tx + bottomX;
                ay = ty + bottomY;
                this.ctx.lineTo(ax, ay);
                this.ctx.strokeStyle = c || 'black';
                this.ctx.stroke();

                this.ctx.restore();
            }
            drawFrame(idx) {
                this.reset();
                let frame = this.frames[idx];
                frame.segments.forEach(x => {
                    this.drawRect(x)
                })
                frame.tables.forEach(x => {
                    this.drawRect(x)
                })
                frame.nodes.forEach(x => {
                    this.drawCircle(x)
                })
                frame.arrow.forEach(x => {
                    this.drawArrow(x)
                })
                this.current = idx;
            }
            drawNextFrame() {
                if (this.current + 1 < this.frames.length) {
                    this.drawFrame(++this.current);
                }
            }
            drawPrevFrame() {
                if (this.current - 1 >= 0) {
                    this.drawFrame(--this.current);
                }
            }
            drawRect(r) {
                this.ctx.save();
                // this.ctx.translate(0, CANVAS_HEIGHT - RECT_WIDTH);

                this.ctx.fillStyle = r.c;
                this.ctx.beginPath();
                this.ctx.rect(r.x, r.y, r.w, r.h);
                this.ctx.fill();
                this.ctx.lineWidth = .25;
                this.ctx.stroke();

                this.ctx.fillStyle = '#ffffff';
                this.ctx.font = '1em Arial';
                this.ctx.textAlign = 'center';
                this.ctx.fillText(r.v, r.vx, r.vy);
                this.ctx.restore();

            }
            drawCircle(r) {
                this.ctx.save();

                this.ctx.fillStyle = r.c;
                this.ctx.beginPath();
                this.ctx.arc(r.x, r.y, r.r, 0, (Math.PI / 180) * 360, false);
                this.ctx.fill();
                this.ctx.lineWidth = .25;
                this.ctx.stroke();

                this.ctx.fillStyle = '#ffffff';
                this.ctx.font = '1em Arial';
                this.ctx.textAlign = 'center';
                this.ctx.fillText(r.v, r.vx, r.vy);

                this.ctx.restore();
            }
            addFrame(obj) {
                let { segments, tables, nodes } = { ...obj };
                const frame = {};
                if (segments) {
                    const array = [];
                    let x = 0;
                    for (let i = 0; i < segments.length; i++) {
                        let h = RECT_HEIGHT;
                        let y = 0;
                        let c = '#7FDBFF';
                        let w = segments[i].size === 0 ? RECT_WIDTH : (segments[i].size * (RECT_WIDTH + SPACEING) - SPACEING);
                        array.push(new Rect(
                            x, y, w, h, c,
                            segments[i].v, x + w / 2, y + h / 2 + 5
                        ));
                        x += w + SPACEING;
                    }
                    frame.segments = array;
                }
                if (tables) {
                    const array = [];
                    let x = 0;
                    for (let i = 0; i < tables.length; i++) {
                        let w = RECT_WIDTH;
                        if (tables[i] !== "empty") {
                            let h = RECT_HEIGHT;
                            let y = RECT_HEIGHT + SPACEING;
                            let c = '#26A65B';
                            array.push(new Rect(
                                x, y, w, h, c,
                                tables[i].v, x + w / 2, y + h / 2 + 5
                            ));
                        }
                        x += w + SPACEING;
                    }
                    frame.tables = array;
                }
                if (nodes) {
                    const array = [];
                    const arrow = [];
                    let x = 0;
                    for (let i = 0; i < nodes.length; i++) {
                        let r = RECT_WIDTH / 2;
                        let y = (RECT_HEIGHT + SPACEING) * 2;
                        if (nodes[i] !== "empty") {
                            let c = '#2C3E50';
                            let data = nodes[i].data;
                            for (let j = 0; j < data.length; j++) {
                                array.push(new Circle(
                                    x + r, y + r, r, c,
                                    data[j], x + r, y + r + 5
                                ));
                                arrow.push(new Arrow(x + r, y + RECT_WIDTH, x + r, y + RECT_WIDTH + ARROW_LENGTH, 'blank'));
                                y += (RECT_WIDTH + ARROW_LENGTH);
                            }
                        }
                        x += RECT_WIDTH + SPACEING;
                    }
                    frame.nodes = array;
                    frame.arrow = arrow;
                }

                this.frames.push(frame);
                //addToSelect(this.frames.length - 1, round)
            }
            clearFrames() {
                this.frames = [];
                this.current = 0;
                //clearSelect();
            }
            playFrames() {
                if (this.current == this.frames.length - 1) {
                    return;
                }
                this.drawFrame(++this.current);
                setTimeout(() => {
                    this.playFrames();
                }, 300);
            }
        }

        const dd = new Draw();

        function loadData() {
            fetch("http://localhost:8080/cv7/map/tree").then(r => r.json()).then(data => {
                let length = data.buckets.length;
                let segments = [];
                let tables = [];
                let nodes = [];
                for (let i = 0; i < data.buckets.length; i++) {
                    let s = data.buckets[i];
                    segments.push({ v: i, size: s.length });
                    if (s.length > 0) {
                        for (let j = 0; j < s.length; j++) {
                            tables.push({ v: j });
                            let n = s[j];
                            if (n.length > 0) {
                                nodes.push({ v: j, data: n.map(e => e) })
                            } else {
                                nodes.push("empty");
                            }
                        }
                    } else {
                        tables.push("empty");
                        nodes.push("empty");
                    }
                }
                dd.clearFrames();
                dd.addFrame({ segments: segments, tables: tables, nodes: nodes });
                dd.drawFrame(0);
            });
        }
        loadData();
    </script>
</body>

</html>